package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.search.executor.ExpireThreadExecutor;
import com.atguigu.tingshu.search.factory.ScheduleTaskThreadPoolFactory;
import com.atguigu.tingshu.search.repository.AlbumInfoIndexRepository;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.runnable.RebuildBloomFilterRunnable;
import com.atguigu.tingshu.search.service.ItemService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import com.google.common.hash.BloomFilter;
import jakarta.annotation.PostConstruct;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Supplier;

@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class ItemServiceImpl implements ItemService {

    @Autowired
    private AlbumInfoIndexRepository albumInfoIndexRepository;

    @Autowired
    private SuggestIndexRepository suggestIndexRepository;

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private UserInfoFeignClient userInfoFeignClient;

    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RBloomFilter rBloomFilter;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private ItemServiceImpl itemServiceImpl;


    ExecutorService executorService = Executors.newFixedThreadPool(4);

    ThreadLocal<String> reentrantLockTokenThreadLocal = new ThreadLocal<>();

    BloomFilter<Long> bloomFilter = null;

    @PostConstruct // spring在创建ItemServiceImpl Bean对象的时候，在生命周期走到初始化前这个阶段就会回调该方法
    public void initRebuildBloomFilter() {

        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);

        //scheduledExecutorService.scheduleWithFixedDelay(new RebuildBloomFilterRunnable(redissonClient, redisTemplate, itemServiceImpl), 0, 7, TimeUnit.DAYS);

        //scheduledExecutorService.schedule(new RebuildBloomFilterRunnable(redissonClient, redisTemplate, itemServiceImpl),10,TimeUnit.SECONDS);

        // 从当前开始算7天之后的凌晨两点执行第一次。
        ScheduleTaskThreadPoolFactory instance = ScheduleTaskThreadPoolFactory.getInstance();

        Long taskFirstTime = instance.diffTime(System.currentTimeMillis());
        instance.execute(new RebuildBloomFilterRunnable(redissonClient, redisTemplate, itemServiceImpl), taskFirstTime, TimeUnit.MICROSECONDS);
    }


//    public void initLocalBloomFilter() {
//        // 1.初始化布隆过滤器
//        Funnel<Long> longFunnel = Funnels.longFunnel();
//        bloomFilter = BloomFilter.create(longFunnel, 1000000, 0.01);
//
//        // 2.将元素放到布隆过滤器中
//        List<Long> albumInfoIdList = getAlbumInfoIdList();
//        albumInfoIdList.stream().forEach(bloomFilter::put);
//        log.info("本地布隆过滤器初始化成功,且布隆中的元素个数为：{}", bloomFilter.approximateElementCount());
//    }


    @SneakyThrows
    @Override
    public void albumOnSale(Long albumId) {

        long start = System.currentTimeMillis();

        CountDownLatch countDownLatch = new CountDownLatch(4);

        // 1. 创建文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        // 2. 给albumInfoIndex属性赋值
        Future<Long> submit = executorService.submit(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                //2.1 远程调用专辑微服务查询基本信息
                Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfoAndAttrValue(albumId);
                AlbumInfo albumInfoData = albumInfoResult.getData();
                if (albumInfoData == null) {
                    log.info("albumInfoFeignClient远程调用专辑微服务失败");
                    throw new GuiguException(201, "远程调用失败");
                }
                // 专辑id
                albumInfoIndex.setId(albumId);
                // 专辑标题
                albumInfoIndex.setAlbumTitle(albumInfoData.getAlbumTitle());
                // 专辑简介
                albumInfoIndex.setAlbumIntro(albumInfoData.getAlbumIntro());
                // 专辑的封面
                albumInfoIndex.setCoverUrl(albumInfoData.getCoverUrl());
                // 专辑包含的声音集数
                albumInfoIndex.setIncludeTrackCount(albumInfoData.getIncludeTrackCount());
                // 专辑是否完结
                albumInfoIndex.setIsFinished(albumInfoData.getIsFinished().toString());
                // 专辑的付费类型（免费、vip免费、付费）
                albumInfoIndex.setPayType(albumInfoData.getPayType());
                // 专辑保存到ES的时间
                albumInfoIndex.setCreateTime(new Date());
                List<AlbumAttributeValue> albumAttributeValueVoList = albumInfoData.getAlbumAttributeValueVoList();
                List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(albumAttributeValueVo -> {
                    AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                    attributeValueIndex.setAttributeId(albumAttributeValueVo.getAttributeId());
                    attributeValueIndex.setValueId(albumAttributeValueVo.getValueId());
                    return attributeValueIndex;
                }).toList();
                // 专辑的属性值集合
                albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
                countDownLatch.countDown();
                return albumInfoData.getUserId();
            }
        });

        executorService.execute(new Runnable() {
            @SneakyThrows
            @Override
            public void run() {
                try {
                    //2.2 远程查询用户信息
                    Long userId = submit.get();
                    Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(userId);
                    UserInfoVo userInfoVoData = userInfoVoResult.getData();
                    Assert.notNull(userInfoVoData, "userInfoFeignClient远程调用用户微服务失败");
                    // 专辑的主播名字
                    albumInfoIndex.setAnnouncerName(userInfoVoData.getNickname());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                } finally {
                    countDownLatch.countDown();
                }
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //2.3 远程查询专辑分类信息
                Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getAlbumCategory(albumId);
                BaseCategoryView categoryViewResultData = baseCategoryViewResult.getData();
                Assert.notNull(categoryViewResultData, "albumInfoFeignClient远程调用专辑微服务失败");
                // 专辑一级分类ID
                albumInfoIndex.setCategory1Id(categoryViewResultData.getCategory1Id());
                // 专辑二级分类ID
                albumInfoIndex.setCategory2Id(categoryViewResultData.getCategory2Id());
                // 专辑三级分类ID
                albumInfoIndex.setCategory3Id(categoryViewResultData.getCategory3Id());
                countDownLatch.countDown();
            }
        });

        executorService.execute(new Runnable() {
            @Override
            public void run() {
                //2.4 远程查询专辑统计信息
                Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumStat(albumId);
                AlbumStatVo albumStatVoData = albumStatVoResult.getData();
                Assert.notNull(albumStatVoData, "albumInfoFeignClient远程调用专辑微服务失败");
                // 专辑的播放量
                Integer playStatNum = getPlayStatNum(albumStatVoData);
                Integer subscribeStatNum = albumStatVoData.getSubscribeStatNum();
                Integer buyStatNum = albumStatVoData.getBuyStatNum();
                Integer commentStatNum = albumStatVoData.getCommentStatNum();
                albumInfoIndex.setPlayStatNum(playStatNum);
                // 专辑的订阅数
                albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
                // 专辑的购买量
                albumInfoIndex.setBuyStatNum(buyStatNum);
                // 专辑的评论数
                albumInfoIndex.setCommentStatNum(commentStatNum);
                // 专辑的热度分
                //Double hotScore = commentStatNum * 0.5 + subscribeStatNum * 0.3 + buyStatNum * 0.2 + playStatNum * 0.1;
                //albumInfoIndex.setHotScore(hotScore);
                albumInfoIndex.setHotScore(new Random().nextDouble());
                countDownLatch.countDown();
            }
        });

        countDownLatch.await();
        long end = System.currentTimeMillis();
        log.info("ID为{}的专辑上架成功,耗时{}毫秒", albumId, end - start);
        // 3. 将文档对象存储到es
        albumInfoIndexRepository.save(albumInfoIndex);
        // 向suggestInfo索引库中保存数据
        SuggestIndex suggestIndex = new SuggestIndex();
        suggestIndex.setId(albumInfoIndex.getId().toString());
        suggestIndex.setTitle(albumInfoIndex.getAlbumTitle());

        suggestIndex.setKeyword(new Completion(new String[]{albumInfoIndex.getAlbumTitle()}));
        suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle())}));
        suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(albumInfoIndex.getAlbumTitle())}));

        suggestIndexRepository.save(suggestIndex);

    }

    @Override
    public void albumOffSale(Long albumId) {
        try {
            albumInfoIndexRepository.deleteById(albumId);
            log.info("ID为{}的专辑下架成功", albumId);
        } catch (Exception e) {
            log.error("ID为{}的专辑下架失败", albumId);
        }
    }

    @Override
    public void batchAlbumOffSale() {
        try {
            albumInfoIndexRepository.deleteAll();
            log.info("批量下架专辑成功");
        } catch (Exception e) {
            log.error("批量下架专辑失败");
        }

    }

    /**
     * 分布式缓存Redis+Redis版本的分布式锁 解决缓存击穿的问题
     *
     * @param albumId
     * @return
     */
    @Override
    public Map<String, Object> getAlbumInfo(Long albumId) {
        return getAlbumInfoFromDb(albumId);
    }


    @SneakyThrows
    private Map getDistroCacheAndLockFinallyRedissonVersion2(Long albumId) {

        // 1.定义缓存key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        long ttl = 0;

        // 2.查询分布式布隆过滤器
        boolean contains = rBloomFilter.contains(albumId);
        if (!contains) {
            return null;
        }
        // 3.查询缓存
        String jsonStrFromRedis = redisTemplate.opsForValue().get(cacheKey);

        // 3.1 缓存命中
        if (StringUtils.hasText(jsonStrFromRedis)) {
            return JSONObject.parseObject(jsonStrFromRedis, Map.class);
        }
        // 3.2 缓存未命中 查询数据库
        // 3.2.1 添加分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        // 非阻塞 还能续期
        boolean acquireLockFlag = lock.tryLock();
        if (acquireLockFlag) {
            try {
                // 3.2.2 回源查询数据
                HashMap<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
                if (albumInfoFromDb != null) {
                    ttl = 60 * 60 * 24 * 7L;
                } else {
                    ttl = 60 * 60 * 24L;
                }
                // 3.2.3 同步数据到缓存中去
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb), ttl, TimeUnit.SECONDS);
                return albumInfoFromDb;
            } finally {
                lock.unlock();
            }
        } else {
            // 等同步时间之后 查询缓存即可
            Thread.sleep(200);
            String result = redisTemplate.opsForValue().get(cacheKey);
            if (!StringUtils.hasText(result)) {
                return JSONObject.parseObject(result, Map.class);
            }
            return getAlbumInfoFromDb(albumId);
        }
    }


    /**
     * 集成Redisson分布式布隆过滤器 以及Redisson分布式锁
     *
     * @param albumId
     * @return
     */
    @SneakyThrows
    private Map getDistroCacheAndLockFinallyRedissonVersion1(Long albumId) {

        // 1.定义缓存key
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        String lockKey = RedisConstant.CACHE_LOCK_SUFFIX + albumId;
        long ttl = 0;

        // 2.查询分布式布隆过滤器
        boolean contains = rBloomFilter.contains(albumId);
        if (!contains) {
            return null;
        }
        // 3.查询缓存
        String jsonStrFromRedis = redisTemplate.opsForValue().get(cacheKey);

        // 3.1 缓存命中
        if (StringUtils.hasText(jsonStrFromRedis)) {
            return JSONObject.parseObject(jsonStrFromRedis, Map.class);
        }
        // 3.2 缓存未命中 查询数据库
        // 3.2.1 添加分布式锁
        RLock lock = redissonClient.getLock(lockKey);
        // 非阻塞 还能续期
        boolean acquireLockFlag = lock.tryLock();
        if (acquireLockFlag) {
            try {
                // 3.2.2 回源查询数据
                HashMap<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
                if (albumInfoFromDb != null) {
                    ttl = 60 * 60 * 24 * 7L;
                } else {
                    ttl = 60 * 60 * 24L;
                }
                // 3.2.3 同步数据到缓存中去
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb), ttl, TimeUnit.SECONDS);
                return albumInfoFromDb;
            } finally {
                lock.unlock();
            }
        } else {
            // 等同步时间之后 查询缓存即可
            Thread.sleep(200);
            String result = redisTemplate.opsForValue().get(cacheKey);
            if (!StringUtils.hasText(result)) {
                return JSONObject.parseObject(result, Map.class);
            }
            return getAlbumInfoFromDb(albumId);
        }
    }


    /**
     * finally version
     * 缓存击穿(分布式锁) 缓存 缓存穿透(固定值攻击：缓存key-null 随机值攻击使用的是本地布隆过滤器)
     * Redis分布式锁+自旋可重入+双缓存查询的缓存使用。
     *
     * @param albumId
     * @return // 续期：
     * 要做的：只要抢到锁的线程没有把自己的活干完，这个抢到锁的线程对应的这个key就不能释放掉。只能等抢到锁的线程把活干完了或者干活期间出异常，才让让这个锁过期。
     * // 活没干完----给锁key续期
     * // 活干完或者干期间出异常----不用再给锁key续期
     * 注意：只有抢到锁，才续期，没抢到就别续。
     * <p>
     * // 启动一个线程--->负责完成续期任务。
     * <p>
     * // 方案1：new Thread线程 让这个线程做续期任务(一直做)   并且让这个线程作为守护线程。 最后再利用Thread的中断机制，完成对续期线程的取消。
     * // 方案2：用线程完成续期。【定时或者延时任务的线程池实现】
     * <p>
     * // 续期：每隔多久 再让redis中的锁key的时间是一个新值。每隔10s给redis中的锁key设置为30s.
     */
    private Map getDistroCacheAndLockFinallyRedisVersion(Long albumId) {
        // 1.定义变量
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        String token = "";
        String lockKey = RedisConstant.ALBUM_LOCK_SUFFIX + albumId;
        Boolean accquireLockFlag = false;

//        // 1.1 查询布隆过滤器（本地的）
//        boolean b = bloomFilter.mightContain(albumId); //解决缓存穿透的随机值攻击
//        if (!b){
//            log.info("布隆过滤器判断：{}", albumId);
//            return null;
//        }
        //1.2 查询布隆过滤器（分布式的）
        boolean bloomContains = rBloomFilter.contains(albumId);
        if (!bloomContains) {
            return null;
        }

        // 2.查询分布式缓存
        String resultFromCache = redisTemplate.opsForValue().get(cacheKey);
        // 3.判断缓存是否存在
        if (!StringUtils.isEmpty(resultFromCache)) {
            return JSONObject.parseObject(resultFromCache, Map.class);
        }
        // 4.缓存未命中 准备查询数据库
        // 4.1 从ThreadLocal中获取令牌值（解决递归的线程进来）
        String s = reentrantLockTokenThreadLocal.get();
        // 4.2 如果是递归进来的线程
        if (!StringUtils.isEmpty(s)) {
            //递归进入
            token = s;
            accquireLockFlag = true;
        } else {
            // 4.3 第一次进来
            token = UUID.randomUUID().toString().replace("-", "");
            // 4.4 加分布式锁
            accquireLockFlag = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 30, TimeUnit.SECONDS);
        }
        // 5.加分布式锁成功
        if (accquireLockFlag) {
            // 开始续期
            ExpireThreadExecutor expireThreadExecutor = new ExpireThreadExecutor(redisTemplate, albumId);
            expireThreadExecutor.renewal(30L, TimeUnit.SECONDS);

            HashMap<String, Object> albumInfoFromDb;
            try {
                long ttl = 0l;
                // 5.1 回源查询数据库
                albumInfoFromDb = getAlbumInfoFromDb(albumId); // Map中有数据---->给该数据在Redis中存储一个较长的时间      Map中没有数据---->给{}在Redis中存储一个较短的时间
                if (albumInfoFromDb != null && albumInfoFromDb.size() > 0) {
                    ttl = 60 * 60 * 24 * 7;
                } else {
                    ttl = 60 * 60 * 24;
                }
                // 5.2 将查询到的数据保存到redis中
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb), ttl, TimeUnit.SECONDS);// 缓存穿透值的固定攻击解决
            } finally {
                // 5.3 释放分布式锁
                // 判断是不是自己加的锁，是自己加的锁 才删除 否则不能删除
                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList(lockKey), token);
                if (execute == 0) {
                    log.error("分布式锁删除失败");
                } else {
                    log.info("分布式锁删除成功");
                }
                //5.4 从ThreadLocal中移除令牌
                reentrantLockTokenThreadLocal.remove();//删除threadLocal中的数据，防止内存泄漏问题
                //5.5 释放续期线程
                expireThreadExecutor.cancelRenewal();
            }
            // 5.6 返回数据给前端
            return albumInfoFromDb;
        } else {
            // 6.加分布式锁失败
            // 6.1.等同步时间
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 6.2 查询缓存--正常99%的情况下200ms之后的缓存一定是有数据。所以直接返回去即可
            String firstCacheStr = redisTemplate.opsForValue().get(cacheKey);

            if (StringUtils.hasText(firstCacheStr)) {
                return JSONObject.parseObject(firstCacheStr, Map.class);
            }
            // 6.3 解决1%极端情况（抢到锁的线程在将数据库中的数据同步到缓存的时候出了问题，导致缓存没有）
            while (true) {
                // 通过监控工具排查定位cpu飙升原因  // 兜底---自旋就是while[自旋+可重入锁]
                // 6.4 查询缓存的作用：只要是为了解决，递归进去的线程将数据同步到缓存之后，其它线程还要抢锁。
                String doubleCacheStr = redisTemplate.opsForValue().get(cacheKey);
                // 6.5 如果有，要在while..true中抢锁的线程不用再抢锁。直接将递归进去的线程放到缓存中的数据返回即可。
                if (StringUtils.hasText(doubleCacheStr)) {
                    return JSONObject.parseObject(doubleCacheStr);
                }
                // 6.6 抢锁
                Boolean acquireLock = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 30, TimeUnit.SECONDS);
                if (acquireLock) {
                    // 将该线程加锁的令牌存放到ThreadLocal，主要保证递归进行的线程不再加锁。【保证可重入锁】
                    reentrantLockTokenThreadLocal.set(token);
                    break;//退出循环
                }
            }
            // 7. 重新递归进去查询数据库
            return getAlbumInfo(albumId);
        }
    }

    /**
     * v3版本：解决锁的误删
     * 解决：每个线程加锁都给一个锁标识 然后在释放锁的时候 先判断一下锁是不是自己加的 如果是则删除 如果不是 则不能删除。
     * 问题：判断锁和释放锁不是原子操作。
     * 解决办法：自定义lua脚本的表达式来将判断锁和释放锁做成原子操作。从而保证锁的误删真正解决。
     */
    @Nullable
    private Map getDistroCacheAndLockV3(Long albumId) {
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        String token = UUID.randomUUID().toString().replace("-", "");
        String lockKey = RedisConstant.ALBUM_LOCK_SUFFIX + albumId;
        // 1.查询分布式缓存
        String resultFromCache = redisTemplate.opsForValue().get(cacheKey);
        // 2.判断缓存是否存在
        if (!StringUtils.isEmpty(resultFromCache)) {
            return JSONObject.parseObject(resultFromCache, Map.class);
        }
        // 3.缓存未命中 回源查询数据库
        // 3.1 加分布式锁
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, token, 30, TimeUnit.SECONDS);
        if (aBoolean) {
            HashMap<String, Object> albumInfoFromDb;
            try {
                // 3.2 回源查询数据库
                albumInfoFromDb = getAlbumInfoFromDb(albumId);
                // 3.3 将查询到的数据保存到redis中
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            } finally {
                // 3.4 释放分布式锁
                // 判断是不是自己加的锁，是自己加的锁 才删除 否则不能删除
                String lockValueFromCache = redisTemplate.opsForValue().get(lockKey);

                String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                Long execute = redisTemplate.execute(new DefaultRedisScript<Long>(luaScript, Long.class), Arrays.asList(lockKey), token);
                if (execute == 0) {
                    log.error("分布式锁删除失败");
                } else {
                    log.info("分布式锁删除成功");
                }
                if (token.equals(lockValueFromCache)) {
                    redisTemplate.delete(lockKey);
                }
            }
            // 3.5 返回数据给前端
            return albumInfoFromDb;
        } else {
            // 4. 等同步时间
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String resultFromCacheStr = redisTemplate.opsForValue().get(cacheKey);
            return JSONObject.parseObject(resultFromCacheStr, Map.class);
        }
    }


    /**
     * v2版本：解决在极端情况下，抢到锁的线程，刚执行业务，断电导致Redis中的锁key没有被释放，那么就会导致死锁发生
     * 解决办法：
     * 1. 客户端主动删除锁 （redisTemplate.delete(lockKey)）
     * 2. Redis服务端给锁key过期时间 （redisTemplate.expire(lockKey,30,TimeUnit.SECONDS)）
     * <p>
     * v2版本问题：
     * 在极端情况下，抢到锁的线程，正准备给锁key设置过期时间的时候断电了，那么还是会出现死锁问题。
     * 解决办法：将加锁和给锁设置过期时间的两步动作做成一个原子操作。
     * redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 30, TimeUnit.SECONDS);
     * 底层实现：redis将设置key和给这个key设置过期时间用一个lua脚本包装起来 然后用一个Redis连接执行这个lua脚本 从而保证这个两个动作的原子性。
     */
    @Nullable
    private Map getDistroCacheAndLockV2(Long albumId) {
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        String lockKey = RedisConstant.ALBUM_LOCK_SUFFIX + albumId;
        // 1.查询分布式缓存
        String resultFromCache = redisTemplate.opsForValue().get(cacheKey);
        // 2.判断缓存是否存在
        if (!StringUtils.isEmpty(resultFromCache)) {
            return JSONObject.parseObject(resultFromCache, Map.class);
        }
        // 3.缓存未命中 回源查询数据库
        // 3.1 加分布式锁
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 30, TimeUnit.SECONDS);
        if (aBoolean) {
            HashMap<String, Object> albumInfoFromDb;
            try {
                // 3.2 回源查询数据库
                albumInfoFromDb = getAlbumInfoFromDb(albumId);
                // 3.3 将查询到的数据保存到redis中
                redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
                // 3.4 释放分布式锁
            } finally {
                redisTemplate.delete(lockKey);
            }
            // 3.5 返回数据给前端
            return albumInfoFromDb;
        } else {
            // 4. 等同步时间
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String resultFromCacheStr = redisTemplate.opsForValue().get(cacheKey);
            return JSONObject.parseObject(resultFromCacheStr, Map.class);
        }
    }


    /**
     * 分布式缓存Redis+Redis版本的分布式锁 解决缓存击穿问题。
     * v1版本
     */
    @Nullable
    private Map getDistroCacheAndLockV1(Long albumId) {
        String cacheKey = RedisConstant.CACHE_INFO_PREFIX + albumId;
        String lockKey = RedisConstant.ALBUM_LOCK_SUFFIX + albumId;
        // 1.查询分布式缓存
        String resultFromCache = redisTemplate.opsForValue().get(cacheKey);
        // 2.判断缓存是否存在
        if (!StringUtils.isEmpty(resultFromCache)) {
            return JSONObject.parseObject(resultFromCache, Map.class);
        }
        // 3.缓存未命中 回源查询数据库1
        // 3.1 加分布式锁
        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(lockKey, "lock");
        redisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);
        if (aBoolean) {
            // 3.2 回源查询数据库
            HashMap<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
            // 3.3 将查询到的数据保存到redis中
            redisTemplate.opsForValue().set(cacheKey, JSONObject.toJSONString(albumInfoFromDb));
            // 3.4 释放分布式锁
            redisTemplate.delete(lockKey);
            // 3.5 返回数据给前端
            return albumInfoFromDb;
        } else {
            // 4. 等同步时间
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            String resultFromCacheStr = redisTemplate.opsForValue().get(cacheKey);
            return JSONObject.parseObject(resultFromCacheStr, Map.class);
        }
    }

//    /**
//     * 使用双缓存架构
//     * 本地Map缓存+分布式Redis缓存
//     */
//    @Override
//    public Map<String, Object> getAlbumInfo(Long albumId) {
//        Map<String, Object> result = new HashMap<>();
//        // 1.查询一级缓存Map
//        boolean b = localCache.containsKey(albumId);
//        if (b) {
//            return localCache.get(albumId);
//        }
//        log.info("未命中一级缓存，开始查询二级缓存");
//        // 2.一级缓存中没有，查询二级缓存Redis
//        String s = redisTemplate.opsForValue().get(albumId.toString());
//        // 3. 分布式缓存命中
//        if (!StringUtils.isEmpty(s)) {
//            // 3.1将Redis中的数据同步到一级本地缓存中
//            result = JSONObject.parseObject(s, Map.class);
//        } else {
//            // 4.二级缓存未命中，查询数据库
//            log.info("未命中二级缓存，开始查询数据库");
//            result = getAlbumInfoFromDb(albumId);
//            if (!CollectionUtils.isEmpty(result)) {
//                // 5.将数据同步到分布式缓存中
//                redisTemplate.opsForValue().set(albumId.toString(), JSONObject.toJSONString(result));
//            }
//        }
//        // 6.将数据同步到一级本地缓存中
//        localCache.put(albumId, result);
//        // 7.返回数据
//        return result;
//    }

//    /**
//     * 使用分布式缓存+异步组合
//     */
//    @Override
//    public Map<String, Object> getAlbumInfo(Long albumId) {
//        long startTime = System.currentTimeMillis();
//        // 1.查询分布式缓存
//        String s = redisTemplate.opsForValue().get(albumId.toString());
//        // 2.判断分布式缓存是否命中
//        if (!StringUtils.isEmpty(s)){
//            log.info("命中分布式缓存，耗时{}毫秒", System.currentTimeMillis() - startTime);
//            return JSONObject.parseObject(s,Map.class);
//        }
//        // 3.分布式缓存未命中，回源查询数据库
//        HashMap<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
//        // 4.将回源查询到的数据写入到分布式缓存中
//        redisTemplate.opsForValue().set(albumId.toString(),JSONObject.toJSONString(albumInfoFromDb),30, TimeUnit.MINUTES);
//        long endTime = System.currentTimeMillis();
//        log.info("未命中分布式缓存，耗时{}毫秒", endTime - startTime);
//        return albumInfoFromDb;
//    }


//    /**
//     * v1 本地缓存版本
//     */
//    HashMap<Long, Map<String, Object>> localCache = new HashMap<>();
//    @SneakyThrows
//    @Override
//    public Map<String, Object> getAlbumInfo(Long albumId) {
//        long startTime = System.currentTimeMillis();
//        // 1.查询本地缓存
//        boolean result = localCache.containsKey(albumId);
//        // 1.1 本地缓存命中，直接返回数据给前端
//        if (result) {
//            log.info("命中本地缓存，耗时{}毫秒", System.currentTimeMillis() - startTime);
//            return localCache.get(albumId);
//        }
//        // 1.2 本地缓存未命中，回源(查询数据库)
//        HashMap<String, Object> albumInfoFromDb = getAlbumInfoFromDb(albumId);
//        // 1.3 将数据存入本地缓存
//        localCache.put(albumId, albumInfoFromDb);
//        long endTime = System.currentTimeMillis();
//        log.info("未命中本地缓存，回源查询，耗时{}毫秒", endTime - startTime);
//        return albumInfoFromDb;
//    }

    @NotNull
    private HashMap<String, Object> getAlbumInfoFromDb(Long albumId) {
        // 1.创建Map对象
        HashMap<String, Object> map = new HashMap<>();

        CompletableFuture<Long> albumCompletableFuture = CompletableFuture.supplyAsync(new Supplier<Long>() {
            @Override
            public Long get() {
                log.info("执行查询专辑任务用的线程：{}", Thread.currentThread().getName());
                // 专辑基本数据
                Result<AlbumInfo> albumInfoAndAttrValue = albumInfoFeignClient.getAlbumInfoAndAttrValue(albumId);
                AlbumInfo albumInfoData = albumInfoAndAttrValue.getData();
                if (albumInfoData == null) {
                    throw new GuiguException(201, "远程查询专辑微服务获取专辑基本信息失败");
                }
                map.put("albumInfo", albumInfoData);
                return albumInfoData.getUserId();
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> categoryCompletableFuture = CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                log.info("执行查询专辑分类任务用的线程：{}", Thread.currentThread().getName());
                // 专辑的分类（分类的名字）
                Result<BaseCategoryView> categoryViewResult = albumInfoFeignClient.getAlbumCategory(albumId);
                BaseCategoryView baseCategoryViewData = categoryViewResult.getData();
                if (baseCategoryViewData == null) {
                    throw new GuiguException(201, "远程查询专辑微服务获取专辑分类信息失败");
                }
                map.put("baseCategoryView", baseCategoryViewData);
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> albumStatCompletableFuture = CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                log.info("执行查询专辑统计任务用的线程：{}", Thread.currentThread().getName());
                // 专辑的统计信息
                Result<AlbumStatVo> albumStat = albumInfoFeignClient.getAlbumStat(albumId);
                AlbumStatVo albumStatVoData = albumStat.getData();
                if (albumStatVoData == null) {
                    throw new GuiguException(201, "远程查询专辑微服务获取专辑统计信息失败");
                }
                map.put("albumStatVo", albumStatVoData);
            }
        }, threadPoolExecutor);

        CompletableFuture<Void> userInfoCompletableFuture = albumCompletableFuture.thenAcceptAsync(new Consumer<Long>() {
            @Override
            public void accept(Long userId) {
                log.info("执行查询主播信息任务用的线程：{}", Thread.currentThread().getName());
                // 专辑的主播信息
                Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(userId);
                UserInfoVo userInfoVoData = userInfoVoResult.getData();
                if (userInfoVoData == null) {
                    throw new GuiguException(201, "远程查询用户微服务获取专辑的主播信息失败");
                }
                map.put("announcer", userInfoVoData);
            }
        }, threadPoolExecutor);

        CompletableFuture.allOf(
                albumCompletableFuture,
                userInfoCompletableFuture,
                categoryCompletableFuture,
                albumStatCompletableFuture).join();

        return map;
    }

    @SneakyThrows
    @Override
    public void preRankingToCache() {
        // 1. 查询全平台的一级分类id
        Result<List<Long>> c1IdsResult = albumInfoFeignClient.getAllCategory1Id();
        List<Long> c1IdsResultData = c1IdsResult.getData();
        if (CollectionUtils.isEmpty(c1IdsResultData)) {
            throw new GuiguException(201, "远程查询专辑微服务获取全平台的一级分类id失败");
        }
        String[] fiveDimension = {"hotScore", "playStatNum", "subscribeStatNum", "buyStatNum", "commentStatNum"};
        for (Long c1Id : c1IdsResultData) {
            for (String dimension : fiveDimension) {
                SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(srb -> srb
                        .index("albuminfo")
                        .query(qb -> qb
                                .term(tqb -> tqb
                                        .field("category1Id")
                                        .value(c1Id)))
                        .sort(sob -> sob.field(fsb -> fsb.field(dimension)))
                        .size(10), AlbumInfoIndex.class);

                ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();

                for (Hit<AlbumInfoIndex> hit : response.hits().hits()) {
                    AlbumInfoIndex albumInfoIndex = hit.source();
                    albumInfoIndices.add(albumInfoIndex);
                }
                String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
                redisTemplate.opsForHash().put(bigKey, dimension, JSONObject.toJSONString(albumInfoIndices));
            }
        }

    }

    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long c1Id, String sortField) {

        String bigKey = RedisConstant.RANKING_KEY_PREFIX + c1Id;
        String albumInfoIndexVoList = (String) redisTemplate.opsForHash().get(bigKey, sortField);
        if (StringUtils.isEmpty(albumInfoIndexVoList)) {
            throw new GuiguException(201, "排行榜信息不存在");
        }
        List<AlbumInfoIndex> albumInfoIndices = JSONObject.parseArray(albumInfoIndexVoList, AlbumInfoIndex.class);
        return albumInfoIndices.stream().map(albumInfoIndex -> {
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            return albumInfoIndexVo;
        }).toList();
    }

    @Override
    public List<Long> getAlbumInfoIdList() {
        Result<List<Long>> albumInfoIdList = albumInfoFeignClient.getAlbumInfoIdList();
        List<Long> albumInfoIdListData = albumInfoIdList.getData();
        if (CollectionUtils.isEmpty(albumInfoIdListData)) {
            throw new GuiguException(201, "应用中不存在专辑id集合");
        }
        return albumInfoIdListData;
    }

    // 重建布隆过滤器
    @Override
    public Boolean rebuildBloomFilter() {

        // 1. 创建新布隆
        // 2. 初始化新布隆
        // 3. 将数据放到新布隆

        // 4. 删除老布隆的数据
        // 5. 删除老布隆的配置
        // 6. 重新用老布隆的名字换新布隆的名字

        // 新布隆上线就可以使用    rename:重新命名 albumInfoBloomFilterNew albumInfoBloomFilter

        RBloomFilter<Object> albumInfoBloomFilterNew = redissonClient.getBloomFilter("albumInfoBloomFilterNew");

        boolean b = albumInfoBloomFilterNew.tryInit(1000000, 0.001);

        List<Long> albumInfoIdList = getAlbumInfoIdList();

        albumInfoIdList.stream().forEach(albumInfoBloomFilterNew::add);

        String script = " redis.call(\"del\",KEYS[1])" +
                "  redis.call(\"del\",KEYS[2])" +
                "  redis.call(\"rename\",KEYS[3],KEYS[1])" +
                "  redis.call(\"rename\",KEYS[4],KEYS[2]) return 0";
        List<String> asList = Arrays.asList("albumIdBloomFilter", "{albumIdBloomFilter}:config", "albumInfoBloomFilterNew", "{albumInfoBloomFilterNew}:config");
        long execute = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), asList);
        if (execute == 0) {
            log.info("老布隆已经被删除，新布隆上线...");
        }
        return execute == 0;
    }


    /**
     * 异步多线程优化4次远程调用
     */
/*    @SneakyThrows
    @Override
    public void albumOnSale(Long albumId) {

        // 1. 创建文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        ConcurrentHashMap<String, Long> cMap = new ConcurrentHashMap<>();


        Thread threadC = new Thread(() -> {
            //2.3 远程查询专辑分类信息
            Result<BaseCategoryView> baseCategoryViewResult = albumInfoFeignClient.getAlbumCategory(albumId);
            BaseCategoryView categoryViewResultData = baseCategoryViewResult.getData();
            Assert.notNull(categoryViewResultData, "albumInfoFeignClient远程调用专辑微服务失败");
            // 专辑一级分类ID
            albumInfoIndex.setCategory1Id(categoryViewResultData.getCategory1Id());
            // 专辑二级分类ID
            albumInfoIndex.setCategory2Id(categoryViewResultData.getCategory2Id());
            // 专辑三级分类ID
            albumInfoIndex.setCategory3Id(categoryViewResultData.getCategory3Id());
        }, "ThreadC");
        threadC.start();

        Thread threadD = new Thread(() -> {
            //2.4 远程查询专辑统计信息
            Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.getAlbumStat(albumId);
            AlbumStatVo albumStatVoData = albumStatVoResult.getData();
            Assert.notNull(albumStatVoData, "albumInfoFeignClient远程调用专辑微服务失败");
            // 专辑的播放量
            Integer playStatNum = getPlayStatNum(albumStatVoData);
            Integer subscribeStatNum = albumStatVoData.getSubscribeStatNum();
            Integer buyStatNum = albumStatVoData.getBuyStatNum();
            Integer commentStatNum = albumStatVoData.getCommentStatNum();
            albumInfoIndex.setPlayStatNum(playStatNum);
            // 专辑的订阅数
            albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
            // 专辑的购买量
            albumInfoIndex.setBuyStatNum(buyStatNum);
            // 专辑的评论数
            albumInfoIndex.setCommentStatNum(commentStatNum);
            // 专辑的热度分
            //Double hotScore = commentStatNum * 0.5 + subscribeStatNum * 0.3 + buyStatNum * 0.2 + playStatNum * 0.1;
            //albumInfoIndex.setHotScore(hotScore);
            albumInfoIndex.setHotScore(new Random().nextDouble());
        }, "ThreadD");
        threadD.start();

        // 2. 给albumInfoIndex属性赋值
        Thread threadA = new Thread(() -> {
            //2.1 远程调用专辑微服务
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfoAndAttrValue(albumId);
            AlbumInfo albumInfoData = albumInfoResult.getData();
            if (albumInfoData == null) {
                log.info("albumInfoFeignClient远程调用专辑微服务失败");
                throw new GuiguException(201, "远程调用失败");
            }
            // 专辑id
            albumInfoIndex.setId(albumInfoIndex.getId());
            // 专辑标题
            albumInfoIndex.setAlbumTitle(albumInfoData.getAlbumTitle());
            // 专辑简介
            albumInfoIndex.setAlbumIntro(albumInfoData.getAlbumIntro());
            // 专辑的封面
            albumInfoIndex.setCoverUrl(albumInfoData.getCoverUrl());
            // 专辑包含的声音集数
            albumInfoIndex.setIncludeTrackCount(albumInfoData.getIncludeTrackCount());
            // 专辑是否完结
            albumInfoIndex.setIsFinished(albumInfoData.getIsFinished().toString());
            // 专辑的付费类型（免费、vip免费、付费）
            albumInfoIndex.setPayType(albumInfoData.getPayType());
            // 专辑保存到ES的时间
            albumInfoIndex.setCreateTime(new Date());
            List<AlbumAttributeValue> albumAttributeValueVoList = albumInfoData.getAlbumAttributeValueVoList();
            List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(albumAttributeValueVo -> {
                AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                attributeValueIndex.setAttributeId(albumAttributeValueVo.getAttributeId());
                attributeValueIndex.setValueId(albumAttributeValueVo.getValueId());
                return attributeValueIndex;
            }).toList();
            // 专辑的属性值集合
            albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
            cMap.put("userId", albumInfoData.getUserId());
        }, "ThreadA");
        threadA.start();
        threadA.join(); // tomcat线程先不往下走 等threadA线程干完活 tomcat线程才往下走


        Thread threadB = new Thread(() -> {
            //2.2 远程查询用户信息
            Long userId = cMap.get("userId");
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(userId);
            UserInfoVo userInfoVoData = userInfoVoResult.getData();
            Assert.notNull(userInfoVoData, "userInfoFeignClient远程调用用户微服务失败");
            // 专辑的主播名字
            albumInfoIndex.setAnnouncerName(userInfoVoData.getNickname());
        }, "ThreadB");
        threadB.start();


        // 3. 将文档对象存储到es
        albumInfoIndexRepository.save(albumInfoIndex);

    }*/



    /*@Override
    public void albumOnSale(Long albumId) {

        // 1. 创建文档对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();

        // 2. 给albumInfoIndex属性赋值
        // 查询tingshu_album库下的album_info表（只能用rpc远程调用）
        Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfoAndAttrValue(albumId);
        AlbumInfo albumInfoData = albumInfoResult.getData();
        if (albumInfoData == null) {
            log.info("albumInfoFeignClient远程调用专辑微服务失败");
            throw new GuiguException(201, "远程调用失败");
        }
        // 专辑id
        albumInfoIndex.setId(albumInfoIndex.getId());
        // 专辑标题
        albumInfoIndex.setAlbumTitle(albumInfoData.getAlbumTitle());
        // 专辑简介
        albumInfoIndex.setAlbumIntro(albumInfoData.getAlbumIntro());
        // 专辑的封面
        albumInfoIndex.setCoverUrl(albumInfoData.getCoverUrl());
        // 专辑包含的声音集数
        albumInfoIndex.setIncludeTrackCount(albumInfoData.getIncludeTrackCount());
        // 专辑是否完结
        albumInfoIndex.setIsFinished(albumInfoData.getIsFinished().toString());
        // 专辑的付费类型（免费、vip免费、付费）
        albumInfoIndex.setPayType(albumInfoData.getPayType());
        // 专辑保存到ES的时间
        albumInfoIndex.setCreateTime(new Date());

        List<AlbumAttributeValue> albumAttributeValueVoList = albumInfoData.getAlbumAttributeValueVoList();

        List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(albumAttributeValueVo -> {
            AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
            attributeValueIndex.setAttributeId(albumAttributeValueVo.getAttributeId());
            attributeValueIndex.setValueId(albumAttributeValueVo.getValueId());
            return attributeValueIndex;
        }).toList();

        // 专辑的属性值集合
        albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);

        Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfo(albumInfoData.getUserId());
        UserInfoVo userInfoVoData = userInfoVoResult.getData();
        Assert.notNull(userInfoVoData, "userInfoFeignClient远程调用用户微服务失败");

        // 专辑的主播名字
        albumInfoIndex.setAnnouncerName(userInfoVoData.getNickname());


        Result<BaseCategoryView> baseCategoryViewResult =albumInfoFeignClient.getAlbumCategory(albumId);
        BaseCategoryView categoryViewResultData = baseCategoryViewResult.getData();
        Assert.notNull(categoryViewResultData, "albumInfoFeignClient远程调用专辑微服务失败");

        // 专辑一级分类ID
        albumInfoIndex.setCategory1Id(categoryViewResultData.getCategory1Id());
        // 专辑二级分类ID
        albumInfoIndex.setCategory2Id(categoryViewResultData.getCategory2Id());
        // 专辑三级分类ID
        albumInfoIndex.setCategory3Id(categoryViewResultData.getCategory3Id());


        Result<AlbumStatVo>  albumStatVoResult=albumInfoFeignClient.getAlbumStat(albumId);
        AlbumStatVo albumStatVoData = albumStatVoResult.getData();
        Assert.notNull(albumStatVoData, "albumInfoFeignClient远程调用专辑微服务失败");

        // 专辑的播放量
        Integer playStatNum = getPlayStatNum(albumStatVoData);
        Integer subscribeStatNum = albumStatVoData.getSubscribeStatNum();
        Integer buyStatNum = albumStatVoData.getBuyStatNum();
        Integer commentStatNum = albumStatVoData.getCommentStatNum();

        albumInfoIndex.setPlayStatNum(playStatNum);
        // 专辑的订阅数
        albumInfoIndex.setSubscribeStatNum(subscribeStatNum);
        // 专辑的购买量
        albumInfoIndex.setBuyStatNum(buyStatNum);
        // 专辑的评论数
        albumInfoIndex.setCommentStatNum(commentStatNum);
        // 专辑的热度分
        //Double hotScore = commentStatNum * 0.5 + subscribeStatNum * 0.3 + buyStatNum * 0.2 + playStatNum * 0.1;
        //albumInfoIndex.setHotScore(hotScore);
        albumInfoIndex.setHotScore(new Random().nextDouble());

        // 3. 将文档对象存储到es
        albumInfoIndexRepository.save(albumInfoIndex);

    }*/
    private static Integer getPlayStatNum(AlbumStatVo albumStatVoData) {
        return albumStatVoData.getPlayStatNum();
    }
}
